﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RaytracerLib.MathLib;
using RaytracerLib.Rays;
using System.Diagnostics;
using YAXLib;
using RaytracerLib.UVMappers;

namespace RaytracerLib.RenderableObjects
{
    /// <summary>
    /// Torus lays in XZ plane. Y axis is normal.
    /// </summary>
    public class DiscObject : RenderableObject
    {
        private double m_radius;

        public DiscObject(Vector3 a_normal, Vector3 a_right) :
            base(a_right, a_normal)
        {
            Name = "Disc";
            m_uv_mapper = new PlaneUVMapper();
            Update(UpdateFlags.All);
        }

        public Vector3 Normal
        {
            get
            {
                return Up;
            }
        }

        [YAXNode]
        public double Radius
        {
            get
            {
                return m_radius;
            }
            set
            {
                m_radius = value;
                Update(UpdateFlags.BoundBox | UpdateFlags.Matrices);
            }
        }

        internal override Intersection GetIntersection(Intersection a_source_ray_intersection, Ray a_ray)
        {
            Vector3 local_start;
            Vector3 local_dir;
            TransformToLocal(a_ray, out local_dir, out local_start);

            if (OneSide)
            {
                if (local_dir.Y > 0)
                    return Scene.NoIntersection;
            }

            if ((a_source_ray_intersection != null) && (a_source_ray_intersection.SceneObject == this))
                return Scene.NoIntersection;

            if (local_dir.Y.IsAlmostRelativeEquals(0))
                return Scene.NoIntersection;

            double dist = local_start.Y / -local_dir.Y;

            if (dist <= 0)
                return Scene.NoIntersection;
            else
            {
                bool back_hit = (local_dir.Y > 0);

                if (back_hit && OneSide)
                    return Scene.NoIntersection;
                else
                {
                    Vector3 local_pos = local_start + local_dir * dist;

                    if (local_pos.X * local_pos.X + local_pos.Z * local_pos.Z > 1)
                        return Scene.NoIntersection;

                    Vector3 world_pos = LocalToWorld * local_pos;
                    dist = (world_pos - a_ray.Start).Length;

                    return new Intersection()
                    {
                        PrevIntersection = a_source_ray_intersection,
                        SceneObject = this,
                        SourceRay = a_ray,
                        Dist = dist,
                        Scene = Scene,
                        BackHit = back_hit,
                        Pos = world_pos,
                        LocalPos = local_pos
                    };
                }
            }  
        }

        internal override Vector3 GetNormal(Intersection a_intersection)
        {
            if (a_intersection.BackHit)
                return -Normal;
            else
                return Normal;
        }

        public override string ToString()
        {
            return String.Format("Disc: {0}", Name);
        }

        internal override Vector3 GetUVW(Intersection a_intersection)
        {
            return base.GetUVW(a_intersection) * 0.5 + new Vector3(0.5, 0, 0.5);
        }

        internal override Vector2 GetUV(Intersection a_intersection)
        {
            Debug.Assert(a_intersection.UVW.Y.IsAlmostRelativeEquals(0));
            return new Vector2(a_intersection.UVW.X, a_intersection.UVW.Z);
        }

        internal override void GetTangents(Intersection a_intersection,
            out Vector3 a_tangent_x, out Vector3 a_tangent_y)
        {
            a_tangent_x = Right;
            a_tangent_y = Forward;
        }

        protected override Vector3 LocalScale
        {
            get
            {
                return new Vector3(Radius, Radius, Radius);
            }
        }

        protected override AABB GetLocalBoundBox()
        {
            return new AABB(new Vector3(-1, -Constants.MINIMAL_DISTANT, -1),
                            new Vector3(1, Constants.MINIMAL_DISTANT, 1));
        }

        public override void ScaleAbsolute(double a_scale)
        {
            Radius *= a_scale;

            base.ScaleAbsolute(a_scale);
        }
    }
}
